/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.server.behaviours;

import java.awt.geom.Rectangle2D;


import javax.vecmath.Vector2d;
import locusts.common.entities.Actor;

/**
 * Steering to stay withing a rectangular boundry area, or return if
 * the boundry is crossed.
 * 
 * @author Hamish Morgan Morgan
 */
public class BoundryAvoid extends AbstractBehaviour {
    
    private static final double DEFAULT_AVOID_REPULSION_FORCE = 1d;
    protected Rectangle2D boundry;
    protected double left;
    protected double top;
    protected double right;
    protected double bottom;
    protected double repulsionForce;
    
    public BoundryAvoid(double weight, Rectangle2D boundry,
            double repulsionForce) {
        super(weight);
        setBoundry(boundry);
        setRepulsionForce(repulsionForce);
    }
    
    public BoundryAvoid(double weight, Rectangle2D boundry) {
        this(weight, boundry, DEFAULT_AVOID_REPULSION_FORCE);
    }
    
    public void setBoundry(Rectangle2D boundry) {
        if (Double.isNaN(boundry.getX()) || Double.isNaN(boundry.getY())
                || Double.isNaN(boundry.getWidth())
                || Double.isNaN(boundry.getHeight())
                || Double.isInfinite(boundry.getX())
                || Double.isInfinite(boundry.getY())
                || Double.isInfinite(boundry.getWidth())
                || Double.isInfinite(boundry.getHeight())) {
            throw new IllegalArgumentException(
                    "Parameter boundry has infinite or NaN values. Using "
                            + "Double.MAX_VALUE or Double.MIN_VALUE "
                            + "unbounded sides.");
        }
        this.boundry = boundry;
        left = boundry.getMinX();
        top = boundry.getMinY();
        right = boundry.getMaxX();
        bottom = boundry.getMaxY();
    }
    
    public void setRepulsionForce(double repulsionForce) {
        if (Double.isNaN(repulsionForce) || Double.isInfinite(repulsionForce)) {
            throw new IllegalArgumentException(
                    "Parameter repulsionForce must be a finite, real number.");
        }
        this.repulsionForce = repulsionForce;
    }
    
    // Attempt to avoid collision with the outside of a rectangular bounding
    // area. Steer away from all walls in inverse proportion to the distance to
    // it, and head back if the wall is crossed.
    public Vector2d getSteering(final Actor boid) {
        
        final Vector2d steering = new Vector2d();
        if (getWeight() == 0d)
            return steering;
        
        if (boid.getX() < left)
            steering.x = (double)(repulsionForce * (left - boid.getX()));
        else if (boid.getX() > right)
            steering.x = (double)(repulsionForce * (right - boid.getX()));
        else if (boid.getX() == left || boid.getX() == right)
            steering.x = 0f;
        else {
            steering.x += (double)(repulsionForce / (boid.getX() - left));
            steering.x -= (double)(repulsionForce / (right - boid.getX()));
        }
        
        if (boid.getY() < top)
            steering.y = (double)(repulsionForce * (top - boid.getY()));
        else if (boid.getY() > bottom)
            steering.y = (double)(repulsionForce * (bottom - boid.getY()));
        else if (boid.getY() == top || boid.getY() == bottom)
            steering.y = 0f;
        else {
            steering.y += (double)(repulsionForce / (boid.getY() - top));
            steering.y -= (double)(repulsionForce / (bottom - boid.getY()));
        }
        
        // checker steering does not exceed the repulsion force (for distances <
        // 1)
        if (steering.lengthSquared() > repulsionForce * repulsionForce) {
            steering.scale((double)(repulsionForce * repulsionForce
                    / steering.lengthSquared()));
        }
        steering.scale((double)getWeight());
        return steering;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final BoundryAvoid other = (BoundryAvoid) obj;
        if (this.boundry != other.boundry &&
                (this.boundry == null || !this.boundry.equals(other.boundry)))
            return false;
        if (this.left != other.left) return false;
        if (this.top != other.top) return false;
        if (this.right != other.right) return false;
        if (this.bottom != other.bottom) return false;
        if (this.repulsionForce != other.repulsionForce) return false;
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        int hash = super.hashCode();
        hash =
                31 * hash +
                (this.boundry != null ? this.boundry.hashCode() : 0);
        hash =
                31 * hash +
                (int) (Double.doubleToLongBits(this.left) ^
                (Double.doubleToLongBits(this.left) >>> 32));
        hash =
                31 * hash +
                (int) (Double.doubleToLongBits(this.top) ^
                (Double.doubleToLongBits(this.top) >>> 32));
        hash =
                31 * hash +
                (int) (Double.doubleToLongBits(this.right) ^
                (Double.doubleToLongBits(this.right) >>> 32));
        hash =
                31 * hash +
                (int) (Double.doubleToLongBits(this.bottom) ^
                (Double.doubleToLongBits(this.bottom) >>> 32));
        hash =
                31 * hash +
                (int) (Double.doubleToLongBits(this.repulsionForce) ^
                (Double.doubleToLongBits(this.repulsionForce) >>> 32));
        return hash;
    }


}